package com.lancoo.ccas52.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import cn.hutool.setting.SettingUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lancoo.ccas52.entity.*;
import com.lancoo.ccas52.exception.BizException;
import com.lancoo.ccas52.mapper.*;
import com.lancoo.ccas52.pojo.common.PageInfo;
import com.lancoo.ccas52.pojo.constant.GlobalConstant;
import com.lancoo.ccas52.pojo.dto.RedisStudentOptionalClass;
import com.lancoo.ccas52.pojo.dto.ScheduleDto;
import com.lancoo.ccas52.pojo.response.basicPlatform.BaseStudent;
import com.lancoo.ccas52.pojo.vo.*;
import com.lancoo.ccas52.redis.DistributedLockService;
import com.lancoo.ccas52.redis.RedisMQPublisher;
import com.lancoo.ccas52.service.*;
import com.lancoo.ccas52.util.*;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.Thread.sleep;

/**
 * <p>
 * 学生 服务实现类
 * </p>
 *
 * @author quin.song
 * @since 2023-01-05
 */
@Service
@Slf4j
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    @Resource
    private TaskInfoService taskInfoService;
    @Resource
    private TeacherService teacherService;
    @Resource
    private CssTaskInfoService cssTaskInfoService;
    @Resource
    private TeachingClassService teachingClassService;
    @Resource
    private TeachingClassWeekService teachingClassWeekService;
    @Resource
    private CssRangeService cssRangeService;
    @Resource
    private CssTaskRoundService cssTaskRoundService;
    @Resource
    private PlanTeachingService planTeachingService;

    @Resource
    private StudentMapper studentMapper;
    @Resource
    private ScheduleMapper scheduleMapper;
    @Resource
    private TeachingClassStudentMapper teachingClassStudentMapper;
    @Resource
    private PlanTeachingMapper planTeachingMapper;
    @Resource
    private PlanTeachingStudentMapper planTeachingStudentMapper;
    @Resource
    private CssProhibitStudentMapper cssProhibitStudentMapper;

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private DistributedLockService distributedLockService;
    @Resource
    private RedisScript<Long> selectCourseLua;
    @Resource
    private RedisScript<Long> cancelCourseLua;


    @Override
    public PageInfo<Student> getStudentsByTeachingClassId(IPage page, Long teachingClassId) {
        IPage<Student> studentByTeachingClassId = studentMapper.getStudentByTeachingClassId(page, Collections.singletonList(teachingClassId));
        return PagingUtil.formatPageResult(studentByTeachingClassId);
    }

    @Override
    public PageInfo<Student> getStudentsCourse(IPage page, Integer flag, Long courseId, Integer studyNature, Integer teachingClassType) {
        //获取都有哪些教学班
        List<TeachingClass> teachingClassList = teachingClassService.getByCourseIdAndStudyNature(flag, courseId, studyNature, teachingClassType);
        List<Long> teachingClassIds = teachingClassList.stream().map(TeachingClass::getTeachingClassId).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(teachingClassIds)) {
            IPage<Student> studentByTeachingClassId = studentMapper.getStudentByTeachingClassId(page, teachingClassIds);
            return PagingUtil.formatPageResult(studentByTeachingClassId);
        }
        return null;
    }

    @Override
    public int getStudentCountByCourse(Integer flag, Long courseId, Integer studyNature, Integer teachingClassType) {
        return studentMapper.getStudentCountByCourse(flag, courseId, studyNature, teachingClassType);
    }

    @Override
    public Student getStudentByBaseStudentId(Integer taskId, String baseStudentId) {
        Student student = this.getOne(new LambdaQueryWrapper<Student>().eq(Student::getTaskId, taskId).eq(Student::getBaseStudentId, baseStudentId));
        return student;
    }

    @Override
    public boolean isProhibitStudent(Long studentId) {
        CssProhibitStudent cssProhibitStudent = cssProhibitStudentMapper.selectOne(new LambdaQueryWrapper<CssProhibitStudent>().eq(CssProhibitStudent::getStudentId, studentId));
        if (cssProhibitStudent != null) {
            return true;
        }
        return false;
    }

    @Override
    public PageInfo<Student> getStudentByConditions(Integer taskId, IPage page, String collegeId, String majorId, String gradeId, String classId, String keyWord, Integer selectionScope) {
        IPage<Student> studentByConditions = studentMapper.getStudentByConditions(taskId, page, collegeId, majorId, gradeId, classId, keyWord, selectionScope);
        return PagingUtil.formatPageResult(studentByConditions);
    }

    @Override
    public PageInfo<Student> getStudentJoinLimit(Integer taskId, IPage page, String collegeId, String majorId, String gradeId, Integer studyNature, String keyWord, Integer state) {
        IPage<Student> studentJoinLimit = studentMapper.getStudentJoinLimit(taskId, page, collegeId, majorId, gradeId, studyNature, keyWord, state);
        return PagingUtil.formatPageResult(studentJoinLimit);
    }

    @Override
    public PageInfo<StudentProhibitVo> getProhibitStudent(Integer cssTaskId, IPage page, String collegeId, String gradeId, String keyWord) {
        IPage<StudentProhibitVo> prohibitStudent = studentMapper.getProhibitStudent(cssTaskId, page, collegeId, gradeId, keyWord);
        return PagingUtil.formatPageResult(prohibitStudent);
    }

    @Override
    public Boolean updateStudent(List<Long> studentIds, Integer selectionScope) {
        LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<Student>().in(Student::getStudentId, studentIds)
                .set(Student::getSelectionScope, selectionScope);
        return this.update(updateWrapper);
    }

    @Override
    public PageInfo<StudentCourseVo> getDeselectStudent(Integer taskId, IPage page, String collegeId, String majorId, String gradeId, String classId, String keyWord) {
        //获取退选轮次的开始时间
        CssTaskInfo cssTaskInfo = cssTaskInfoService.getCssTaskInfo(taskId, 2);
        CssTaskRound cssTaskRound = cssTaskRoundService.getOne(new LambdaQueryWrapper<CssTaskRound>()
                .eq(CssTaskRound::getCssTaskId, cssTaskInfo.getCssTaskId())
                .eq(CssTaskRound::getSelectRound, 3));
        if (ObjUtil.isNull(cssTaskRound)) {
            throw new BizException(5, "请先设置退选学生选课时段");
        }
        IPage<StudentCourseVo> selectStudent = studentMapper.getSelectStudent(taskId, page, collegeId, majorId, gradeId, classId, keyWord, 1, 4, cssTaskRound.getBeginTime());
        return PagingUtil.formatPageResult(selectStudent);
    }

    @Override
    public List<StudentTeachingClass> getSelectedByStudentId(Integer taskId, Long studentId, Integer teachingClassType) {
        Student student = this.getById(studentId);
        //已选教学班 包括主修 辅修
        //获取全部可选教学班
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(taskId, teachingClassType, student);
        //过滤已选
        List<RedisStudentOptionalClass> selectedClass = redisStudentOptionalClassHashMap.values().stream().filter(a -> ObjUtil.isNotNull(a.getFlag()) && a.getFlag() == 1).collect(Collectors.toList());
        List<StudentTeachingClass> mergedList = this.getTeachingClassDetail(taskId, selectedClass.stream().map(RedisStudentOptionalClass::getTeachingClassId).collect(Collectors.toList()));
        //赋值学生属性
        mergedList.forEach(teachingClass -> {
            RedisStudentOptionalClass redisStudentOptionalClass = redisStudentOptionalClassHashMap.get(teachingClass.getTeachingClassId());
            teachingClass.setFlag(redisStudentOptionalClass.getFlag());
            teachingClass.setCourseFlag(redisStudentOptionalClass.getCourseFlag());
        });
        //环节教学班 非在线选课教学班
        mergedList.addAll(getNormalTeachingClass(taskId, studentId));
        return mergedList;
    }

    @Override
    public SelectionRequireVo getSelectionRequire(Integer taskId, Long studentId, Integer studyNature) {
        SelectionRequireVo selectionRequireVo = new SelectionRequireVo();
        int maxCourse;
        double maxTotalPoint;
        Student student = studentMapper.getStudentInfoById(studentId, studyNature);
        if (ObjUtil.isNull(student)) {
//            String prompt = studyNature == 1 ? "你不在主修教学计划内" : "你不在辅修教学计划内";
//            throw new BizException(5, prompt);
            return selectionRequireVo;
        }
        maxCourse = studyNature == 1 ? student.getCourseLimitMain() : student.getCourseLimitMinor();
        maxTotalPoint = studyNature == 1 ? student.getCreditLimitMain() : student.getCreditLimitMinor();
        //选课范围 默认在线选课。预选没有选课范围
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(taskId, 1, student);
        //过滤已选
        List<RedisStudentOptionalClass> selectedClass = redisStudentOptionalClassHashMap.values().stream().filter(a -> ObjUtil.isNotNull(a.getFlag()) && a.getFlag() == 1).collect(Collectors.toList());
        List<StudentTeachingClass> selectedTeachingClass = this.getTeachingClassDetail(taskId, selectedClass.stream().map(RedisStudentOptionalClass::getTeachingClassId).collect(Collectors.toList()))
                .stream().filter(a -> a.getStudyNature().equals(studyNature)).collect(Collectors.toList());
        selectionRequireVo.setMaxCourse(maxCourse)
                .setMaxTotalPoint(maxTotalPoint)
                .setCurrentCourse(selectedTeachingClass.size())
                .setCurrentTotalPoint(selectedTeachingClass.stream().mapToDouble(StudentTeachingClass::getCredit).sum())
                .setSelectionScope(student.getSelectionScope());
        return selectionRequireVo;

    }

    @Override
    public PageInfo<StudentTeachingClass> getAvailableTeachingClass(Integer taskId, Integer currentPage, Integer pageSize, Long studentId, Integer teachingClassType, String courseTypeId, Integer courseNature, Double credit, Integer studyNature, String keyWord, Integer isExist, Integer roundId) {
        if (teachingClassType.equals(1)) {
            return this.getAvailableTeachingClassOnline(taskId, currentPage, pageSize, studentId, teachingClassType, courseTypeId, courseNature, credit, studyNature, keyWord, isExist, roundId);
        } else if (teachingClassType.equals(3)) {
            return this.getAvailableTeachingClassPre(taskId, currentPage, pageSize, studentId, teachingClassType, courseTypeId, courseNature, credit, studyNature, keyWord, isExist);
        }
        return new PageInfo<>();
    }

    /**
     * 在线选课 可选教学班
     *
     * @param taskId
     * @param currentPage
     * @param pageSize
     * @param studentId
     * @param teachingClassType
     * @param courseTypeId
     * @param courseNature
     * @param credit
     * @param studyNature
     * @param keyWord
     * @param isExist
     * @param roundId
     * @return
     */
    public PageInfo<StudentTeachingClass> getAvailableTeachingClassOnline(Integer taskId, Integer currentPage, Integer pageSize, Long studentId, Integer teachingClassType, String courseTypeId, Integer courseNature, Double credit, Integer studyNature, String keyWord, Integer isExist, Integer roundId) {
        //获取学生信息 后者获取可选教学班需要其中的学院、年级、专业信息
        Student student = this.getById(studentId);
        //获取选课轮次 用于筛选后面范围设置要显示的教学班
        CssTaskRound cssTaskRound = cssTaskRoundService.getById(roundId);
        //退选阶段 只显示已选的教学班
        if (cssTaskRound.getSelectRound().equals(3)) {
            return this.getDeselectTeachingClass(student, taskId, currentPage, pageSize, courseTypeId, courseNature, credit, studyNature, keyWord, isExist);
        }
        //获取选课范围 一对多范围设置
        List<CssRange> cssRangeList = cssRangeService.lambdaQuery().eq(CssRange::getRoundId, roundId).list();
        //如果该学生不属于选课范围任何学院、任何年级则返回空
        boolean hasMatchingCssRange = false;
        for (CssRange cssRange : cssRangeList) {
            Integer optionIdentify = cssRange.getOptionIdentify();
            String optionId = cssRange.getOptionId();
            if ((optionIdentify.equals(1) && optionId.equals(student.getCollegeId()))
                    || (optionIdentify.equals(2) && optionId.equals(student.getGradeId()))) {
                hasMatchingCssRange = true;
                break;
            }
        }
        if (!hasMatchingCssRange) {
            return new PageInfo<>();
        }
        //课程范围为空 则直接返回空数据
        List<CssRange> courseRanges = cssRangeList.stream().filter(a -> a.getOptionIdentify().equals(3)).collect(Collectors.toList());
        if (CollUtil.isEmpty(courseRanges) || cssTaskRound.getStudyNature() == null || cssTaskRound.getMajorProp() == null || cssTaskRound.getCourseNature() == null) {
            return new PageInfo<>();
        }
        //判断是否显示重修教学班.只有补选 退选才显示重修教学班
        Integer isRetake = !cssTaskRound.getSelectRound().equals(1) ?
                cssTaskInfoService.lambdaQuery().eq(CssTaskInfo::getTaskId, taskId).eq(CssTaskInfo::getType, 2).one().getIsRetake() : 2;

        //获取全部可选教学班
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(taskId, teachingClassType, student);
        List<StudentTeachingClass> availableTeachingClass = this.getTeachingClassDetail(taskId, new ArrayList<>(redisStudentOptionalClassHashMap.keySet()));
        //赋值学生属性
        availableTeachingClass.forEach(teachingClass -> {
            RedisStudentOptionalClass redisStudentOptionalClass = redisStudentOptionalClassHashMap.get(teachingClass.getTeachingClassId());
            teachingClass.setFlag(redisStudentOptionalClass.getFlag());
            teachingClass.setCourseFlag(redisStudentOptionalClass.getCourseFlag());
        });
        availableTeachingClass = availableTeachingClass.stream()
                //是否 过滤非重修的教学班
                .filter(teachingClass -> isRetake.equals(1) || (!isRetake.equals(1) && teachingClass.getCourseFlag().equals(1)))
                //根据课程范围获取 可选课程
                .filter(teachingClass ->
                        teachingClass.getStudyNature().equals(cssTaskRound.getStudyNature()) &&
                                teachingClass.getMajorProp().equals(cssTaskRound.getMajorProp()) &&
                                (cssTaskRound.getCourseNature().equals(3) || teachingClass.getCourseNature().equals(cssTaskRound.getCourseNature())) &&
                                courseRanges.stream().anyMatch(courseRange -> courseRange.getOptionId().equals(teachingClass.getCourseTypeId())))
                //根据筛选条件筛选可选教学班
                .filter(teachingClass ->
                        (StrUtil.isBlank(courseTypeId) || courseTypeId.equals(teachingClass.getCourseTypeId())) &&
                                (ObjUtil.isNull(courseNature) || courseNature.equals(teachingClass.getCourseNature())) &&
                                ((ObjUtil.isNull(credit) || (credit.equals(4.0) && teachingClass.getCredit() > 3)) || teachingClass.getCredit().equals(credit)) &&
                                (StrUtil.isBlank(keyWord) || teachingClass.getCourseName().contains(keyWord)) &&
                                (ObjUtil.isNull(studyNature) || studyNature.equals(teachingClass.getStudyNature()))
                )
                //筛选有容量的教学班
                .filter(teachingClass -> !isExist.equals(1) || teachingClass.getStudentNumber() < teachingClass.getClassNum())
                .collect(Collectors.toList());
        return PagingUtil.getPages(currentPage, pageSize, availableTeachingClass);
    }

    /**
     * 预选课程 可选教学班
     *
     * @param taskId
     * @param currentPage
     * @param pageSize
     * @param studentId
     * @param teachingClassType
     * @param courseTypeId
     * @param courseNature
     * @param credit
     * @param studyNature
     * @param keyWord
     * @param isExist
     * @return
     */
    public PageInfo<StudentTeachingClass> getAvailableTeachingClassPre(Integer taskId, Integer currentPage, Integer pageSize, Long studentId, Integer teachingClassType, String courseTypeId, Integer courseNature, Double credit, Integer studyNature, String keyWord, Integer isExist) {
        Student student = this.getById(studentId);

        //获取全部可选教学班
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(taskId, teachingClassType, student);
        List<StudentTeachingClass> availableTeachingClass = this.getTeachingClassDetail(taskId, new ArrayList<>(redisStudentOptionalClassHashMap.keySet()));
        availableTeachingClass = availableTeachingClass.stream()
                //根据筛选条件筛选可选教学班
                .filter(teachingClass ->
                        (StrUtil.isBlank(courseTypeId) || courseTypeId.equals(teachingClass.getCourseTypeId())) &&
                                (ObjUtil.isNull(courseNature) || courseNature.equals(teachingClass.getCourseNature())) &&
                                ((ObjUtil.isNull(credit) || (credit.equals(4.0) && teachingClass.getCredit() > 3)) || teachingClass.getCredit().equals(credit)) &&
                                (StrUtil.isBlank(keyWord) || teachingClass.getCourseName().contains(keyWord)) &&
                                (ObjUtil.isNull(studyNature) || studyNature.equals(teachingClass.getStudyNature()))
                )
                //筛选有容量的教学班
                .filter(teachingClass -> isExist.equals(0) || (isExist.equals(1) && teachingClass.getStudentNumber() < teachingClass.getClassNum()))
                .collect(Collectors.toList());
        //赋值学生属性
        availableTeachingClass.forEach(teachingClass -> {
            RedisStudentOptionalClass redisStudentOptionalClass = redisStudentOptionalClassHashMap.get(teachingClass.getTeachingClassId());
            teachingClass.setFlag(redisStudentOptionalClass.getFlag());
            teachingClass.setCourseFlag(redisStudentOptionalClass.getCourseFlag());
        });
        return PagingUtil.getPages(currentPage, pageSize, availableTeachingClass);
    }

    /**
     * 退选阶段 获取所有教学班
     *
     * @param student
     * @param taskId
     * @param currentPage
     * @param pageSize
     * @param courseTypeId
     * @param courseNature
     * @param credit
     * @param studyNature
     * @param keyWord
     * @param isExist
     * @return
     */
    public PageInfo<StudentTeachingClass> getDeselectTeachingClass(Student student, Integer taskId, Integer currentPage, Integer pageSize, String courseTypeId, Integer courseNature, Double credit, Integer studyNature, String keyWord, Integer isExist) {
        //获取全部可选教学班
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(taskId, 1, student);
        //过滤已选
        List<RedisStudentOptionalClass> selectedClass = redisStudentOptionalClassHashMap.values().stream().filter(a -> ObjUtil.isNotNull(a.getFlag()) && a.getFlag() == 1).collect(Collectors.toList());
        List<StudentTeachingClass> availableTeachingClass = this.getTeachingClassDetail(taskId, selectedClass.stream().map(RedisStudentOptionalClass::getTeachingClassId).collect(Collectors.toList()));
        availableTeachingClass = availableTeachingClass.stream()
                //根据筛选条件筛选可选教学班
                .filter(teachingClass ->
                        (StrUtil.isBlank(courseTypeId) || courseTypeId.equals(teachingClass.getCourseTypeId())) &&
                                (ObjUtil.isNull(courseNature) || courseNature.equals(teachingClass.getCourseNature())) &&
                                ((ObjUtil.isNull(credit) || (credit.equals(4.0) && teachingClass.getCredit() > 3)) || teachingClass.getCredit().equals(credit)) &&
                                (StrUtil.isBlank(keyWord) || teachingClass.getCourseName().contains(keyWord)) &&
                                (ObjUtil.isNull(studyNature) || studyNature.equals(teachingClass.getStudyNature()))
                )
                .collect(Collectors.toList());
        //赋值学生属性
        availableTeachingClass.forEach(teachingClass -> {
            RedisStudentOptionalClass redisStudentOptionalClass = redisStudentOptionalClassHashMap.get(teachingClass.getTeachingClassId());
            teachingClass.setFlag(redisStudentOptionalClass.getFlag());
            teachingClass.setCourseFlag(redisStudentOptionalClass.getCourseFlag());
        });
        return PagingUtil.getPages(currentPage, pageSize, availableTeachingClass);
    }

    /**
     * 获取学生可选课程
     *
     * @param taskId
     * @param baseStudentId
     * @param studyNature
     * @param selectionScope
     * @param courseTypeId
     * @param courseNature
     * @param credit
     * @param keyWord
     * @return
     */
    public List<TeachingClassCourseDto> getAvailableCourse(Integer taskId, String baseStudentId, Integer studyNature, Integer selectionScope, String courseTypeId, Integer courseNature, Double credit, String keyWord) {
        /*
        获取学生是否跨专业。
        本专业/年级    学生获取教学计划 本专业本年级 对应所有课程的教学班
        本专业跨年级
        本院系跨年级
        全部专业/年级
        选课范围 1本专业年级  2本专业跨年级 3本院系跨年级 4全部专业年级
         */
        List<TeachingClassCourseDto> courseByPlan = new ArrayList<>();
        //获取学生的教学计划  主修的话有一个  辅修的话可能有两个，但是其专业年级学院一样，获取第一个就行
        PlanTeaching teachingPlanByStu = planTeachingStudentMapper.getTeachingPlanByStu(taskId, baseStudentId, studyNature);
        if (ObjUtil.isNotNull(teachingPlanByStu)) {
            //根据选课范围获取教学计划 对应课程
            switch (selectionScope) {
                case 1:
                    courseByPlan = planTeachingMapper.getCourseByPlan(taskId, courseTypeId, courseNature, credit, keyWord, null, teachingPlanByStu.getBaseMajorId(), teachingPlanByStu.getGradeId());
                    break;
                case 2:
                    courseByPlan = planTeachingMapper.getCourseByPlan(taskId, courseTypeId, courseNature, credit, keyWord, null, teachingPlanByStu.getBaseMajorId(), null);
                    break;
                case 3:
                    courseByPlan = planTeachingMapper.getCourseByPlan(taskId, courseTypeId, courseNature, credit, keyWord, teachingPlanByStu.getCollegeId(), null, null);
                    break;
                default:
                    courseByPlan = planTeachingMapper.getCourseByPlan(taskId, courseTypeId, courseNature, credit, keyWord, null, null, null);
            }
        }
        //获取学生重修的课程
        TaskInfo taskInfo = taskInfoService.getById(taskId);
        List<TeachingClassCourseDto> courseRetake = planTeachingMapper.getCourseRetake(taskId, taskInfo.getSchoolId(), taskInfo.getTerm(), courseTypeId, courseNature, credit, keyWord, baseStudentId, studyNature);
        return Stream.concat(courseByPlan.stream(), courseRetake.stream()).distinct().collect(Collectors.toList());
    }

    //获取可选教学班 只存储学生教学班基础信息
    public HashMap<Long, RedisStudentOptionalClass> getAvailableTeachingClass(Integer taskId, Integer teachingClassType, Student student) {

        //获取学生可选教学班ids 包含主修辅修
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassSet = new HashMap();
        if (redisUtil.hasKey(getCacheKeys(taskId, student.getStudentId(), 4, teachingClassType == 1 ? "Online" : "Primary"))) {
            redisUtil.hmget(getCacheKeys(taskId, student.getStudentId(), 4, teachingClassType == 1 ? "Online" : "Primary")).forEach((k, v) -> {
                redisStudentOptionalClassSet.put(Long.parseLong(k.toString()), (RedisStudentOptionalClass) v);
            });
        } else {
            //获取选课课程 预选selectionScope=1?
            List<TeachingClassCourseDto> availableCourse = this.getAvailableCourse(taskId, student.getBaseStudentId(), null, student.getSelectionScope(), null, null, null, null);
            //非重修教学班
            List<Long> courseIds = availableCourse.stream().filter(a -> a.getState().equals(1)).map(TeachingClassCourseDto::getCourseId).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(courseIds)) {
                studentMapper.getTeachingClass(teachingClassType, courseIds, null, student.getStudentId())
                        .forEach(a -> {
                            redisStudentOptionalClassSet.put(a.getTeachingClassId(), RedisStudentOptionalClass.builder()
                                    .teachingClassId(a.getTeachingClassId())
                                    .courseId(a.getCourseId())
                                    .flag(a.getFlag())
                                    .courseFlag(1).build());
                        });
            }
            //重修课程教学班
            List<Long> courseRetakeIds = availableCourse.stream().filter(a -> a.getState().equals(2)).map(TeachingClassCourseDto::getCourseId).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(courseRetakeIds)) {
                studentMapper.getRetakeTeachingClass(teachingClassType, courseRetakeIds, null, student.getStudentId())
                        .forEach(a -> {
                            redisStudentOptionalClassSet.put(a.getTeachingClassId(), RedisStudentOptionalClass.builder()
                                    .teachingClassId(a.getTeachingClassId())
                                    .courseId(a.getCourseId())
                                    .flag(a.getFlag())
                                    .courseFlag(2).build());
                        });
            }
            //转化格式
            Map<String, Object> resultMap = new HashMap<>();
            for (Map.Entry<Long, RedisStudentOptionalClass> optionalClassEntry : redisStudentOptionalClassSet.entrySet()) {
                resultMap.put(optionalClassEntry.getKey().toString(), optionalClassEntry.getValue());

            }
            redisUtil.hmset2(getCacheKeys(taskId, student.getStudentId(), 4, teachingClassType == 1 ? "Online" : "Primary"), resultMap, 60 * 60 * 24 * 7);
        }
        return redisStudentOptionalClassSet;
    }

    @Override
    public StudentTeachingClassView getAvailableTeachingClass(GetAvailableCondition getAvailableCondition) {
        if (getAvailableCondition.getTeachingClassType().equals(1)) {
            return this.getAvailableTeachingClassOnline(getAvailableCondition);
        } else if (getAvailableCondition.getTeachingClassType().equals(3)) {
            return this.getAvailableTeachingClassPre(getAvailableCondition);
        }
        return new StudentTeachingClassView();
    }


    public StudentTeachingClassView getAvailableTeachingClassOnline(GetAvailableCondition condition) {
        StudentTeachingClassView studentTeachingClassView = new StudentTeachingClassView();
        //获取全部可选教学班 放到最前面，先计算已选教学班，选课要求
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(condition.getTaskId(), condition.getTeachingClassType(), condition.getStudentId(), condition.getBaseStudentId(), condition.getSelectionScope());
        List<StudentTeachingClass> availableTeachingClass = this.getTeachingClassDetail(condition.getTaskId(), new ArrayList<>(redisStudentOptionalClassHashMap.keySet()));
        //赋值学生属性
        availableTeachingClass.forEach(teachingClass -> {
            RedisStudentOptionalClass redisStudentOptionalClass = redisStudentOptionalClassHashMap.get(teachingClass.getTeachingClassId());
            teachingClass.setFlag(redisStudentOptionalClass.getFlag());
            teachingClass.setCourseFlag(redisStudentOptionalClass.getCourseFlag());
        });
        //过滤已选
        List<StudentTeachingClass> selectedClass = availableTeachingClass.stream().filter(a -> ObjUtil.isNotNull(a.getFlag()) && a.getFlag() == 1).collect(Collectors.toList());
        studentTeachingClassView.setSelectedTeachingClassList(selectedClass);
        //当前选课数量
        studentTeachingClassView.setCurrentCourse(selectedClass.stream().filter(a -> a.getStudyNature().equals(condition.getStudyNature())).count());
        //当前总学分
        studentTeachingClassView.setCurrentTotalPoint(selectedClass.stream().filter(a -> a.getStudyNature().equals(condition.getStudyNature())).mapToDouble(StudentTeachingClass::getCredit).sum());

        //正选补选需要过滤轮次设置的条件，退选需要过滤已选教学班
        if (condition.getSelectRound().equals(3)) {
            availableTeachingClass = availableTeachingClass.stream().filter(a -> ObjUtil.isNotNull(a.getFlag()) && a.getFlag() == 1).collect(Collectors.toList());
        } else {
            availableTeachingClass = availableTeachingClass.stream()
                    //根据课程范围获取 可选课程
                    .filter(teachingClass ->
                            teachingClass.getStudyNature().equals(condition.getRoundStudyNature()) &&
                                    teachingClass.getMajorProp().equals(condition.getRoundMajorProp()) &&
                                    (condition.getRoundCourseNature().equals(3) || teachingClass.getCourseNature().equals(condition.getRoundCourseNature())) &&
                                    (condition.getCssRanges().stream().anyMatch(courseRange -> courseRange.getOptionId().equals(teachingClass.getCourseTypeId()))))
                    .collect(Collectors.toList());
        }

        //判断是否显示重修教学班.只有补选 退选才显示重修教学班
        if (!condition.getSelectRound().equals(1)) {
            availableTeachingClass = availableTeachingClass.stream()
                    .filter(teachingClass -> condition.getIsRetake().equals(1) || (!condition.getIsRetake().equals(1) && teachingClass.getCourseFlag().equals(1)))
                    .collect(Collectors.toList());
        }

        availableTeachingClass = availableTeachingClass.stream()
                //根据筛选条件筛选可选教学班
                .filter(teachingClass ->
                        (StrUtil.isBlank(condition.getCourseTypeId()) || condition.getCourseTypeId().equals(teachingClass.getCourseTypeId())) &&
                                (ObjUtil.isNull(condition.getCourseNature()) || condition.getCourseNature().equals(teachingClass.getCourseNature())) &&
                                ((ObjUtil.isNull(condition.getCredit()) || (condition.getCredit().equals(4.0) && teachingClass.getCredit() > 3)) || teachingClass.getCredit().equals(condition.getCredit())) &&
                                (StrUtil.isBlank(condition.getKeyWord()) || teachingClass.getCourseName().contains(condition.getKeyWord())) &&
                                (ObjUtil.isNull(condition.getStudyNature()) || condition.getStudyNature().equals(teachingClass.getStudyNature()))
                )
                //筛选有容量的教学班
                .filter(teachingClass -> !condition.getIsExist().equals(1) || teachingClass.getStudentNumber() < teachingClass.getClassNum())
                .collect(Collectors.toList());
        studentTeachingClassView.setPageInfo(PagingUtil.getPages(condition.getCurrentPage(), condition.getPageSize(), availableTeachingClass));
        return studentTeachingClassView;
    }

    public StudentTeachingClassView getAvailableTeachingClassPre(GetAvailableCondition condition) {
        StudentTeachingClassView studentTeachingClassView = new StudentTeachingClassView();
        //获取全部可选教学班
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassHashMap = this.getAvailableTeachingClass(condition.getTaskId(), condition.getTeachingClassType(), condition.getStudentId(), condition.getBaseStudentId(), condition.getSelectionScope());
        List<StudentTeachingClass> availableTeachingClass = this.getTeachingClassDetail(condition.getTaskId(), new ArrayList<>(redisStudentOptionalClassHashMap.keySet()));
        //赋值学生属性
        availableTeachingClass.forEach(teachingClass -> {
            RedisStudentOptionalClass redisStudentOptionalClass = redisStudentOptionalClassHashMap.get(teachingClass.getTeachingClassId());
            teachingClass.setFlag(redisStudentOptionalClass.getFlag());
            teachingClass.setCourseFlag(redisStudentOptionalClass.getCourseFlag());
        });
        //过滤已选
        List<StudentTeachingClass> selectedClass = availableTeachingClass.stream().filter(a -> ObjUtil.isNotNull(a.getFlag()) && a.getFlag() == 1).collect(Collectors.toList());
        studentTeachingClassView.setSelectedTeachingClassList(selectedClass);

        availableTeachingClass = availableTeachingClass.stream()
                //根据筛选条件筛选可选教学班
                .filter(teachingClass ->
                        (StrUtil.isBlank(condition.getCourseTypeId()) || condition.getCourseTypeId().equals(teachingClass.getCourseTypeId())) &&
                                (ObjUtil.isNull(condition.getCourseNature()) || condition.getCourseNature().equals(teachingClass.getCourseNature())) &&
                                ((ObjUtil.isNull(condition.getCredit()) || (condition.getCredit().equals(4.0) && teachingClass.getCredit() > 3)) || teachingClass.getCredit().equals(condition.getCredit())) &&
                                (StrUtil.isBlank(condition.getKeyWord()) || teachingClass.getCourseName().contains(condition.getKeyWord())) &&
                                (ObjUtil.isNull(condition.getStudyNature()) || condition.getStudyNature().equals(teachingClass.getStudyNature()))
                )
                //筛选有容量的教学班
                .filter(teachingClass -> condition.getIsExist().equals(0) || (condition.getIsExist().equals(1) && teachingClass.getStudentNumber() < teachingClass.getClassNum()))
                .collect(Collectors.toList());
        studentTeachingClassView.setPageInfo(PagingUtil.getPages(condition.getCurrentPage(), condition.getPageSize(), availableTeachingClass));
        return studentTeachingClassView;
    }


    //获取可选教学班 只存储学生教学班基础信息
    public HashMap<Long, RedisStudentOptionalClass> getAvailableTeachingClass(Integer taskId, Integer teachingClassType, Long studentId, String baseStudentId, Integer selectionScope) {

        //获取学生可选教学班ids 包含主修辅修
        HashMap<Long, RedisStudentOptionalClass> redisStudentOptionalClassSet = new HashMap();
        if (redisUtil.hasKey(getCacheKeys(taskId, studentId, 4, teachingClassType == 1 ? "Online" : "Primary"))) {
            redisUtil.hmget(getCacheKeys(taskId, studentId, 4, teachingClassType == 1 ? "Online" : "Primary")).forEach((k, v) -> {
                redisStudentOptionalClassSet.put(Long.parseLong(k.toString()), (RedisStudentOptionalClass) v);
            });
        } else {
            //获取选课课程 预选selectionScope=1?
            List<TeachingClassCourseDto> availableCourse = this.getAvailableCourse(taskId, baseStudentId, null, selectionScope, null, null, null, null);
            //非重修教学班
            List<Long> courseIds = availableCourse.stream().filter(a -> a.getState().equals(1)).map(TeachingClassCourseDto::getCourseId).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(courseIds)) {
                studentMapper.getTeachingClass(teachingClassType, courseIds, null, studentId)
                        .forEach(a -> {
                            redisStudentOptionalClassSet.put(a.getTeachingClassId(), RedisStudentOptionalClass.builder()
                                    .teachingClassId(a.getTeachingClassId())
                                    .courseId(a.getCourseId())
                                    .flag(a.getFlag())
                                    .courseFlag(1).build());
                        });
            }
            //重修课程教学班
            List<Long> courseRetakeIds = availableCourse.stream().filter(a -> a.getState().equals(2)).map(TeachingClassCourseDto::getCourseId).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(courseRetakeIds)) {
                studentMapper.getRetakeTeachingClass(teachingClassType, courseRetakeIds, null, studentId)
                        .forEach(a -> {
                            redisStudentOptionalClassSet.put(a.getTeachingClassId(), RedisStudentOptionalClass.builder()
                                    .teachingClassId(a.getTeachingClassId())
                                    .courseId(a.getCourseId())
                                    .flag(a.getFlag())
                                    .courseFlag(2).build());
                        });
            }
            //转化格式
            Map<String, Object> resultMap = new HashMap<>();
            for (Map.Entry<Long, RedisStudentOptionalClass> optionalClassEntry : redisStudentOptionalClassSet.entrySet()) {
                resultMap.put(optionalClassEntry.getKey().toString(), optionalClassEntry.getValue());

            }
            redisUtil.hmset2(getCacheKeys(taskId, studentId, 4, teachingClassType == 1 ? "Online" : "Primary"), resultMap, 60 * 60 * 24 * 7);
        }
        return redisStudentOptionalClassSet;
    }

    //获取教学班详细信息
    public List<StudentTeachingClass> getTeachingClassDetail(Integer taskId, List<Long> teachingClassIds) {
        List<StudentTeachingClass> list = new ArrayList<>();
        if (CollUtil.isEmpty(teachingClassIds)) {
            return list;
        }
        redisUtil.hgets(getCacheKeys(taskId, null, 6, null), teachingClassIds.stream().map(String::valueOf).collect(Collectors.toSet()))
                .forEach(a -> {
                    if (a != null) {
                        list.add((StudentTeachingClass) a);
                    }
                });
        //redis 不存在的数据则查询数据库并缓存
        teachingClassIds.removeAll(list.stream().map(StudentTeachingClass::getTeachingClassId).collect(Collectors.toList()));
        if (CollUtil.isNotEmpty(teachingClassIds)) {
            List<StudentTeachingClass> courseTeachingClass = studentMapper.getNormalCourseTeachingClass(teachingClassIds, null);
            //填充教师信息
            Map<Long, List<Teacher>> teacherMap = teacherService.getTeacherByTeachingClassIds(teachingClassIds).stream()
                    .collect(Collectors.groupingBy(Teacher::getTeachingClassId));
            //课表信息
            List<ScheduleDto> scheduleDtoList = scheduleMapper.getScheduleByTeachingClassIds(null, teachingClassIds);
            Map<Long, List<ScheduleDto>> scheduleMap = scheduleDtoList.stream().collect(Collectors.groupingBy(ScheduleDto::getTeachingClassId));
            //学生人数
            Map<Long, Integer> studentNumMap = teachingClassStudentMapper.getStudentNumber(teachingClassIds)
                    .stream().collect(Collectors.toMap(TeachingClassCourseDto::getTeachingClassId, TeachingClassCourseDto::getStudentNumber));
            courseTeachingClass.forEach(a -> {
                a.setTeachers(teacherMap.get(a.getTeachingClassId()));
                a.setSchedules(scheduleMap.get(a.getTeachingClassId()));
                //学生人数
                a.setStudentNumber(studentNumMap.getOrDefault(a.getTeachingClassId(), 0));
            });
            list.addAll(courseTeachingClass);
            //缓存数据
            if (CollUtil.isNotEmpty(courseTeachingClass)) {
                redisUtil.hmset(getCacheKeys(taskId, null, 6, null), courseTeachingClass.stream().collect(Collectors.toMap(a -> a.getTeachingClassId(), a -> a)), 60 * 60 * 24 * 7);
            }
        }
        list.sort(Comparator.comparing(StudentTeachingClass::getTeachingClassId));
        return list;
    }

    @Cacheable(cacheNames = "StudentTeachingClass:Normal", key = "#taskId+':'+#studentId")
    @Override
    public List<StudentTeachingClass> getNormalTeachingClass(Integer taskId, Long studentId) {
        List<StudentTeachingClass> normalTeachingClass = new ArrayList<>();
        //获取学生都有哪些教学班
        List<Long> classIds = teachingClassStudentMapper.selectList(new LambdaQueryWrapper<TeachingClassStudent>().eq(TeachingClassStudent::getStudentId, studentId).eq(TeachingClassStudent::getFlag, 1))
                .stream().map(TeachingClassStudent::getTeachingClassId).distinct().collect(Collectors.toList());
        if (CollUtil.isNotEmpty(classIds)) {
            //课程教学班
            List<StudentTeachingClass> teachingClassList = studentMapper.getNormalCourseTeachingClass(classIds, 0);
            //环节教学班
            List<StudentTeachingClass> linkTeachingClassList = studentMapper.getNormalSegmentTeachingClass(classIds);
            normalTeachingClass.addAll(teachingClassList);
            normalTeachingClass.addAll(linkTeachingClassList);
        }
        List<Long> teachingClassIds = normalTeachingClass.stream().map(StudentTeachingClass::getTeachingClassId).distinct().collect(Collectors.toList());
        if (CollUtil.isNotEmpty(teachingClassIds)) {
            //填充教师信息
            Map<Long, List<Teacher>> teacherMap = teacherService.getTeacherByTeachingClassIds(teachingClassIds).stream()
                    .collect(Collectors.groupingBy(Teacher::getTeachingClassId));
            //课表信息
            List<ScheduleDto> scheduleDtoList = scheduleMapper.getScheduleByTeachingClassIds(null, teachingClassIds);
            Map<Long, List<ScheduleDto>> scheduleMap = scheduleDtoList.stream().collect(Collectors.groupingBy(ScheduleDto::getTeachingClassId));
            normalTeachingClass.forEach(a -> {
                a.setTeachers(teacherMap.get(a.getTeachingClassId()));
                a.setSchedules(scheduleMap.get(a.getTeachingClassId()));
            });
        }
        return normalTeachingClass;
    }


    @Override
    public StudentTeachingClass isExistSelectCourse(Integer taskId, Long studentId, Integer teachingClassType, Integer studyNature, Long courseId) {
        Student student = this.getById(studentId);
        //是否已选
        Collection<RedisStudentOptionalClass> availableTeachingClass = this.getAvailableTeachingClass(taskId, teachingClassType, student).values();
        RedisStudentOptionalClass redisStudentOptionalClass = availableTeachingClass.stream()
                .filter(e -> ObjUtil.isNotNull(e.getFlag()) && e.getFlag() == 1 && e.getCourseId().equals(courseId)).findFirst().orElse(null);
        if (ObjUtil.isNotNull(redisStudentOptionalClass)) {
            return this.getTeachingClassDetail(taskId, CollUtil.newArrayList(redisStudentOptionalClass.getTeachingClassId())).get(0);
        }
        return null;
    }

    @Override
    public int selectCourseOptimize(AddSelectCourse addSelectCourse) {
        try {
            //构建键值列表
            String teachingClassDetailKey = getCacheKeys(addSelectCourse.getTaskId(), null, 6, null);
            String studentOptionalClassKey = getCacheKeys(addSelectCourse.getTaskId(), addSelectCourse.getStudentId(), 4, addSelectCourse.getTeachingClassType() == 1 ? "Online" : "Primary");
            String teachingClassStudentKey = getCacheKeys(addSelectCourse.getTaskId(), null, 3, null);

            List<String> keys = ListUtil.toLinkedList(teachingClassDetailKey, studentOptionalClassKey, teachingClassStudentKey);
            Long execute = redisTemplate.execute(selectCourseLua, keys, addSelectCourse.getStudentId(), addSelectCourse.getTeachingClassId(),
                    LocalDateTime.now(), addSelectCourse.getCancelTeachingClassId(), addSelectCourse.getTeachingClassType());
            return execute.intValue();
        } catch (RedisConnectionFailureException e) {
            return -2;
        }
    }

    @Override
    public int selectCourse(Integer taskId, Long studentId, Long teachingClassId, Long cancelTeachingClassId) {
        try {
            AtomicInteger resultRef = new AtomicInteger(0); // 初始化为默认值，-1 表示选课失败
            TeachingClass teachingClass = teachingClassService.getById(teachingClassId);
            Student student = this.getById(studentId);
            Integer teachingClassType = teachingClass.getTeachingClassType();
            //是否已选
            HashMap<Long, RedisStudentOptionalClass> availableTeachingClass = this.getAvailableTeachingClass(taskId, teachingClassType, student);
            if (!availableTeachingClass.containsKey(teachingClassId)) {
                return -4;
            }
            RedisStudentOptionalClass redisStudentOptionalClass = availableTeachingClass.get(teachingClassId);
            if (ObjUtil.isNotNull(redisStudentOptionalClass.getFlag()) && redisStudentOptionalClass.getFlag() == 1) {
                return 1;
            }
            // 获取分布式锁
            String lockKey = "lock:hashKey:" + taskId + ":" + teachingClassId;
            distributedLockService.doWithLock(lockKey, () -> {
                StudentTeachingClass studentTeachingClass = this.getTeachingClassDetail(taskId, CollUtil.newArrayList(redisStudentOptionalClass.getTeachingClassId())).get(0);
                if (teachingClassType == 1) {
                    //在线选课
                    if (studentTeachingClass.getStudentNumber() + 1 > studentTeachingClass.getClassNum()) {
                        //选课已满，无法继续选课
                        resultRef.set(-1); // 1 表示选课成功
                    } else {
                        //更新教学班人数
                        studentTeachingClass.setStudentNumber(studentTeachingClass.getStudentNumber() + 1);
                        redisUtil.hset(getCacheKeys(taskId, null, 6, null), teachingClassId.toString(), studentTeachingClass, 60 * 60 * 24 * 7);
                    }
                } else {
                    //更新教学班人数
                    studentTeachingClass.setStudentNumber(studentTeachingClass.getStudentNumber() + 1);
                    redisUtil.hset(getCacheKeys(taskId, null, 6, null), teachingClassId.toString(), studentTeachingClass, 60 * 60 * 24 * 7);
                }
            });
            if (resultRef.get() == -1) {
                return resultRef.get();
            }
            //更新学生可选教学班信息
            redisStudentOptionalClass.setFlag(1);
            redisUtil.hset(getCacheKeys(taskId, student.getStudentId(), 4, teachingClassType == 1 ? "Online" : "Primary"), teachingClassId.toString(), redisStudentOptionalClass, 60 * 60 * 24 * 7);

            if (ObjUtil.isNotNull(cancelTeachingClassId)) {
                // 获取分布式锁
                RedisStudentOptionalClass redisCancelStudentOptionalClass = availableTeachingClass.get(cancelTeachingClassId);
                if (redisCancelStudentOptionalClass.getFlag().equals(1)) {
                    String lockKeyCancel = "lock:hashKey:" + taskId + ":" + cancelTeachingClassId;
                    distributedLockService.doWithLock(lockKeyCancel, () -> {
                        StudentTeachingClass studentCacelTeachingClass = this.getTeachingClassDetail(taskId, CollUtil.newArrayList(redisCancelStudentOptionalClass.getTeachingClassId())).get(0);
                        //更新教学班人数
                        studentCacelTeachingClass.setStudentNumber(studentCacelTeachingClass.getStudentNumber() - 1);
                        redisUtil.hset(getCacheKeys(taskId, null, 6, null), cancelTeachingClassId.toString(), studentCacelTeachingClass, 60 * 60 * 24 * 7);
                    });

                    //更新学生可选教学班信息
                    redisCancelStudentOptionalClass.setFlag(2);
                    redisUtil.hset(getCacheKeys(taskId, student.getStudentId(), 4, teachingClassType == 1 ? "Online" : "Primary"), cancelTeachingClassId.toString(), redisCancelStudentOptionalClass, 60 * 60 * 24 * 7);

                    TeachingClassStudent teachingClassStudentCancel = new TeachingClassStudent();
                    teachingClassStudentCancel.setTeachingClassId(cancelTeachingClassId)
                            .setStudentId(studentId)
                            .setFlag(2)
                            .setModifiedTime(LocalDateTime.now());
                    redisUtil.hset(getCacheKeys(taskId, null, 3, null), studentId + "-" + cancelTeachingClassId, teachingClassStudentCancel);
                }
            }

            TeachingClassStudent teachingClassStudent = new TeachingClassStudent();
            teachingClassStudent.setTeachingClassId(teachingClassId)
                    .setStudentId(studentId)
                    .setFlag(1)
                    .setModifiedTime(LocalDateTime.now());

//            redis发送订阅模式发送消息 处理高并发数据库写的方式，数据库写入在消费者类中完成；
//            redisMQPublisher.studentAddFormalCourse(teachingClassStudent);

            //选课数据先存在redis中，再定时写入数据库
            redisUtil.hset(getCacheKeys(taskId, null, 3, null), studentId + "-" + teachingClassId, teachingClassStudent);


            return 1;
        } catch (RedisConnectionFailureException e) {
            return -2;
        }
    }

    @Override
    public int cancelCourseOptimize(AddCancelCourse addCancelCourse) {
        try {
            //构建键值列表
            String teachingClassDetailKey = getCacheKeys(addCancelCourse.getTaskId(), null, 6, null);
            String studentOptionalClassKey = getCacheKeys(addCancelCourse.getTaskId(), addCancelCourse.getStudentId(), 4, addCancelCourse.getTeachingClassType() == 1 ? "Online" : "Primary");
            String teachingClassStudentKey = getCacheKeys(addCancelCourse.getTaskId(), null, 3, null);

            List<String> keys = ListUtil.toLinkedList(teachingClassDetailKey, studentOptionalClassKey, teachingClassStudentKey);
            Long execute = redisTemplate.execute(cancelCourseLua, keys, addCancelCourse.getStudentId(), addCancelCourse.getTeachingClassId(),
                    LocalDateTime.now(), addCancelCourse.getFlag());
            return execute.intValue();
        } catch (RedisConnectionFailureException e) {
            return -2;
        } catch (Exception e) {
            return -3;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int cancelCourse(Integer taskId, Long studentId, Long teachingClassId, Integer flag) {
        try {
            TeachingClass teachingClass = teachingClassService.getById(teachingClassId);
            Student student = this.getById(studentId);
            //1 在线选课  3预选-没有选课上限
            Integer teachingClassType = teachingClass.getTeachingClassType();
            //是否已选
            HashMap<Long, RedisStudentOptionalClass> availableTeachingClass = this.getAvailableTeachingClass(taskId, teachingClassType, student);
            if (!availableTeachingClass.containsKey(teachingClassId)) {
                return -4;
            }
            RedisStudentOptionalClass redisStudentOptionalClass = availableTeachingClass.get(teachingClassId);
            if (!redisStudentOptionalClass.getFlag().equals(1)) {
                return 1;
            }
            // 获取分布式锁
            String lockKey = "lock:hashKey:" + taskId + ":" + teachingClassId;
            distributedLockService.doWithLock(lockKey, () -> {
                StudentTeachingClass studentTeachingClass = this.getTeachingClassDetail(taskId, CollUtil.newArrayList(redisStudentOptionalClass.getTeachingClassId())).get(0);
                //更新教学班人数
                studentTeachingClass.setStudentNumber(studentTeachingClass.getStudentNumber() - 1);
                redisUtil.hset(getCacheKeys(taskId, null, 6, null), teachingClassId.toString(), studentTeachingClass, 60 * 60 * 24 * 7);
            });

            //更新学生可选教学班信息
            redisStudentOptionalClass.setFlag(flag);
            redisUtil.hset(getCacheKeys(taskId, student.getStudentId(), 4, teachingClassType == 1 ? "Online" : "Primary"), teachingClassId.toString(), redisStudentOptionalClass, 60 * 60 * 24 * 7);

            TeachingClassStudent teachingClassStudent = new TeachingClassStudent();
            teachingClassStudent.setTeachingClassId(teachingClassId)
                    .setStudentId(studentId)
                    .setFlag(flag)
                    .setModifiedTime(LocalDateTime.now());

//            redisMQPublisher.studentDropFormalCourse(teachingClassStudent);

            //选课数据先存在redis中，再定时写入数据库
            redisUtil.hset(getCacheKeys(taskId, null, 3, null), studentId + "-" + teachingClassId, teachingClassStudent);

            return 1;
        } catch (RedisConnectionFailureException e) {
            return -2;
        }
    }

    @Override
    public List<TeachingClassStudent> batchAddStudent(BatchSetStudent batchSetStudent) {
        //do 有必要的话，优化缓存
        List<TeachingClassStudent> teachingClassStudentList = new ArrayList<>();
        batchSetStudent.getStudentIds().forEach(studentId -> teachingClassStudentList.add(new TeachingClassStudent()
                .setTeachingClassId(batchSetStudent.getTeachingClassId())
                .setStudentId(studentId)
                .setFlag(batchSetStudent.getFlag())));
        teachingClassStudentMapper.addBatch(teachingClassStudentList);

        List<String> studentTeachingClassKeys = new ArrayList<>();
        String standby = batchSetStudent.getTeachingClassType() == 1 ? "Online" : "Primary";
        batchSetStudent.getStudentIds().forEach(student -> {
            studentTeachingClassKeys.add(getCacheKeys(batchSetStudent.getTaskId(), student, 4, standby));
        });
        if (studentTeachingClassKeys.size() > 0) {
            redisUtil.del(studentTeachingClassKeys.toArray(new String[0]));
        }
        //删除教学班详情 主要只是为了更新人数
        redisUtil.hdel(getCacheKeys(batchSetStudent.getTaskId(), null, 6, null), batchSetStudent.getTeachingClassId().toString());
        return teachingClassStudentList;
    }

    @Override
    public Boolean syncTeachingClassStudent(Integer taskId) {
        //有必要加锁吗？
        List<TeachingClassStudent> teachingClassStudents = new ArrayList<>();
        Map<Object, Object> teachingClassStudentMap = redisUtil.hmget(getCacheKeys(taskId, null, 3, null));
        for (Object value : teachingClassStudentMap.values()) {
            teachingClassStudents.add((TeachingClassStudent) value);
        }
        if (teachingClassStudents.size() > 0) {
            Consumer<List<TeachingClassStudent>> studentConsumer = o -> teachingClassStudentMapper.addBatchSetTime(o);
            BatchInsert.batchSplitInsert(teachingClassStudents, studentConsumer);
        }
        //同步数据库时，可能有学生选课。所以选择一个一个删除
        List<Object> keys = new ArrayList<>(teachingClassStudentMap.keySet());
        if (keys.size() > 0) {
            redisUtil.hdel(getCacheKeys(taskId, null, 3, null), keys.toArray(new Object[0]));
        }
        return true;
    }

    @Override
    public Boolean clearCacheByTeachingClass(Integer taskId, Long courseId, Integer studyNature, Long teachingClassId) {
        //删除教学班详情
        redisUtil.hdel(getCacheKeys(taskId, null, 6, null), teachingClassId.toString());
        //清空学生可选教学班缓存  先获取可以选该教学班有哪些学生
        List<Student> students = planTeachingService.getStudentNumByCourseAndNature(1, courseId, studyNature, null);

        List<String> studentTeachingClassKeys = new ArrayList<>();
        students.forEach(student -> {
            studentTeachingClassKeys.add(getCacheKeys(taskId, student.getStudentId(), 4, "Online"));
            studentTeachingClassKeys.add(getCacheKeys(taskId, student.getStudentId(), 4, "Primary"));
        });
        if (studentTeachingClassKeys.size() > 0) {
            redisUtil.del(studentTeachingClassKeys.toArray(new String[0]));
        }
        return true;
    }

    public void aa(Integer taskId, Long teachingClassId, Integer teachingClassType) {
        redisUtil.hdel(getCacheKeys(taskId, null, 6, null), teachingClassId.toString());
        //更新教学班信息
        this.getTeachingClassDetail(taskId, CollUtil.newArrayList(teachingClassId));
        //更新该课程涉及到的学生缓存


    }


    @Override
    public void syncBaseStudent(Integer taskId, List<BaseStudent> baseStudents, List<String> gradeList) {
        List<Student> students = new ArrayList<>();
        for (BaseStudent baseStudent : baseStudents) {
            if (gradeList.contains(baseStudent.getGradeID())) {
                students.add(baseStudent.toStudent(taskId));
            }
        }
        //批量插入教学班学生，大数据批量插入
        Consumer<List<Student>> studentConsumer = o -> studentMapper.addBatch(o);
        BatchInsert.batchSplitInsert(students, studentConsumer);
    }


    public String getCacheKeys(Integer taskId, Long uniqueIdentify, Integer type, String standby) {
        /*
        返回值用StringBuilder 速度快
        1:存储教学班都有哪些学生  key->teachingClassId
        2:存储学生都选了哪些教学班，教学班详情  key->studentId
        3:存储教学班对应学生数据，然后同步到数据库
        4:学生可选教学班 key->studentId value -> Set<TeachingClassId>
        5:学生已选教学班
        6：教学班详情 包括课表 余量等
         */
        StringBuilder key = new StringBuilder();
        key.append(GlobalConstant.REDIS_PREFIX).append(taskId).append(StrUtil.C_COLON);
        switch (type) {
            case 1:
                return key.append("TeachingStudentId").append(StrUtil.C_COLON).append(uniqueIdentify).toString();
            case 2:
                return key.append("StudentTeachingClass").append(StrUtil.C_COLON).append(uniqueIdentify).toString();
            case 3:
                return key.append("TeachingClassStudent").toString();
            case 4:
                return key.append("StudentOptionalClass").append(StrUtil.C_COLON).append(uniqueIdentify).append(StrUtil.C_COLON).append(standby).toString();
            case 5:
                return key.append("StudentSelectedClass").append(StrUtil.C_COLON).append(uniqueIdentify).toString();
            case 6:
                return key.append("TeachingClassDetail").toString();
            default:
                return "";
        }
    }

    @Override
    public String deselectStudentToExcel(Integer taskId, String collegeId, String collegeName, String majorId, String majorName, String gradeId, String gradeName, String classId, String className) {
        TaskInfo taskInfo = taskInfoService.getById(taskId);
        String storePath = "/" + taskInfo.getTaskName() + "/退选结果-" + LocalDate.now() + ".xlsx";
        String targetPath = GlobalConstant.OUTPUT_EXCEL_PATH + storePath;

        FileUtil.del(targetPath);
        ExcelWriter writer = ExcelUtil.getWriter(targetPath);

        Page<StudentCourseVo> page = new Page<>(1, -1);
        PageInfo<StudentCourseVo> deselectStudentPage = this.getDeselectStudent(taskId, page, collegeId, majorId, gradeId, classId, null);
        List<StudentCourseVo> resultList = deselectStudentPage.getList();
        String title = "{}退选结果  \n  {}";
        writer.merge(6, StrUtil.format(title, taskInfo.getSchoolName(), taskInfo.getTermName()), false);
        writer.setRowHeight(0, 40);
        String filterTem = "院系:{}  专业:{}  年级:{}  班级:{}   共:{}名学生   导出时间:{} ";
        String filter = StrUtil.format(filterTem, StrUtil.isBlank(collegeId) ? "全部  " : collegeName,
                StrUtil.isBlank(majorId) ? "全部" : majorName,
                StrUtil.isBlank(gradeId) ? "全部" : gradeName,
                StrUtil.isBlank(classId) ? "全部" : className,
                resultList.size(), DateUtil.now());
        writer.merge(6, filter, false);
        List<List<String>> rows = new ArrayList<>();
        rows.add(Arrays.asList("退选学生", "院系", "专业", "年级", "班级", "退选课程", "退选时间"));
        List<String> row;
        for (StudentCourseVo item : resultList) {
            row = Arrays.asList(item.getStudentName(), item.getCollegeName(), item.getMajorName(), item.getGradeName(), item.getClassName(),
                    item.getCourseName(), item.getModifiedTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            rows.add(row);
        }
        writer.write(rows, false);
        ExcelFormatUtil.setFont(writer);
        writer.close();
        return GlobalConstant.ACCESS_PATH + "/static/OutputExcel" + storePath;
    }
}
